home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Language/OS - Multiplatform Resource Library
/
LANGUAGE OS.iso
/
smaltalk
/
st80_r41.lha
/
st80_r41
/
ObjectDebugging.st
< prev
next >
Wrap
Text File
|
1993-07-23
|
33KB
|
1,028 lines
"Welcome to objectDebugging.st. This file contains the complete source to
add lightweight classes and breakpoint methods to your system. Together
these two changes provide new techniques to use while debugging. Since
the code is largely uncommented, these prefatory paragraphs serve as
documentation for now. The following paragraph is a brief introduction to the
purpose of this file, while the one after that explains the interface in more
detail."!
"The notion of a lightweight class comes from the desire to be able to change
the behavior of a particular object without also changing all the other objects
of its class. This can be done by creating a special class for the object, but
that's expensive in both time and space, so there's a need for a kind of class
that's cheap to create and store--and that's what a lightweight class is. The
lightweight class is created between an object and its real class, and so any
methods defined in the lightweight class apply only to a singular object.
Thus, you can modify the add: method of a particular OrderedCollection, say,
without changing all the many other OrderedCollections in the image. One of
the changes you can make in a lightweight class (and in regular classes, too)
is to put a breakpoint on a method. Breakpointing a method is like adding
'self halt' as its first line, but there are some important differences. First, it's
easier to add (and remove) a breakpoint, since it's done by menu rather than
by typing. Second, adding/removing breakpoints doesn't affect the various
change mechanisms, so the change set and change log don't include trivial
changes for putting (and presumably later removing) a halt in a method.
Finally, breakpoints are invisible in source code, so when you edit a
breakpointed method in a browser (or look at it in the debugger), you see
only the normally defined code--the breakpoint itself is transparent.
Combining lightweight classes and breakpoints allows you to set breakpoints
on methods belonging to an individual object. So it's possible to set a
breakpoint on the add: method of one OrderedCollection without bringing the
rest of the system to a grinding halt. That kind of debugging is what our
object debugging package is for."!
"The interface for all this is (I hope) relatively simple. You can send any
object the message 'browseLightweight' to create and edit its lightweight
class. (Alternatively, there's also a browseLightweight menu option from any
Inspector.) This kind of browsing creates a special Lightweight Class
Browser, which looks something like a ClassBrowser, but there are several
differences. First, lightweight classes don't have protocols, so rather than
seeing methods organized into protocols, you just see a list of all methods for
the lightweight class. The method display uses two formatting conventions to
convey information: methods that are defined in the lightweight class have
their selectors printed in boldface, while those with breakpoints have their
selectors preceded by asterisks. Second, in the upper right hand of the
browser is a text pane that lets you choose which methods to list--you can
either show only methods defined in the lightweight class, or all methods up
to some menu-chosen superclass. This option is convenient since it lets you
look at methods as defined in a superclass and then alter and accept them in
the lightweight class. Finally, the lightweight class browser has an inspector
for the object at the bottom, because we believe users will often want to get at
objects and their instance variables in the context of object debugging.
Adding breakpoints is even easier: every method list now has a menu option
called 'breakpoint,' which occurs right above 'move to ...' This option is a toggle
switch--if the method is not breakpointed, a breakpoint will be added
(and the method's selector will be preceded by an asterisk to indicated this.)
If the method is already breakpointed, choosing 'breakpoint' will cause the
existing breakpoint to be removed. We envision the browser will be used as
follows: a programmer finds there's a problem in a particular method, with
some object behaving strangely. So, he or she puts a breakpoint in that
method or some previous one where the object is created. When that
breakpoint is triggered, the programmer opens a debugger and looks at the
problem object in the inspector. Using the browseLightweight menu option,
the programmer changes or puts breakpoints on important methods of the
object, and then proceeds from the debugger. Now he/she can monitor that
one object's behavior and (we hope) discover what the problem is."!
"This code was developed for Smalltalk-80 Release 4.1 for the Macintosh. It
should work on earlier releases with work on the interface, and also on
compatible versions for other platforms. We're interested in feedback on this
package, so please let us know if you have comments, questions, problems,
or ideas. In particular, we're interested in whether you find these changes
useful for debugging, other features you'd like to see added to this package,
other debugging improvements you'd like to see in the Smalltalk
environment, and finally any other uses you have for lightweight classes.
Please direct any such comments or questions to Bob Hinkle at
hinkle@cs.uiuc.edu."!
ByteCodeStream subclass: #CodeStream
instanceVariableNames: 'outerStream needsFrame hybrid usesOuter canCopy tempStores usedArgs usedTemps copiedVars finalNumTemps innerBlocks forContext forNonImmediate forNonSubclassable deferredBlocks method allSourceMaps optimizedBlockNodes methodClass '
classVariableNames: 'RestartSignal '
poolDictionaries: 'OpcodePool '
category: 'System-Compiler-Support'!
!CodeStream methodsFor: 'initialize-release'!
initialize
super initialize.
needsFrame := hybrid := false.
usesOuter := 256. "infinity"
canCopy := true.
methodClass := CompiledMethod!
methodClass
^methodClass!
methodClass: aClass
methodClass := aClass! !
CodeStream allSubInstancesDo:
[ :m | m methodClass isNil ifTrue: [m methodClass: CompiledMethod]].
CodeStream allInstancesDo:
[ :m | m methodClass isNil ifTrue: [m methodClass: CompiledMethod]].!
!CodeStream methodsFor: 'initialize-release'!
makeMethod: methodNode
"Return an appropriate compiled code object"
| deferred |
deferred := deferredBlocks. "makeMethodOfClass: sets it to nil"
self makeMethodOfClass: self methodClass local: false.
method mclass: class.
"Revisit all ambiguous blocks"
deferred == nil
ifFalse:
[deferred do: [:dfb | dfb complete]].
^method! !
!Object methodsFor: 'user interface'!
browseLightweight
LightweightClassBrowser newOnObject: self.
^self!
becomeLightweight
| lwc |
(self dispatchingClass isKindOf: LightweightClass)
ifFalse: [lwc := LightweightClass newWithSuper: self class.
self changeClassToThatOf: lwc basicNew]! !
!Object methodsFor: 'class membership'!
dispatchingClass
<primitive: 111>
self primitiveFailed! !
!CompiledCode methodsFor: 'testing'!
isBreakpoint
^false! !
CompiledCode variableSubclass: #CompiledMethod
instanceVariableNames: 'mclass sourceCode agent '
classVariableNames: ''
poolDictionaries: ''
category: 'Kernel-Methods'!
!CompiledMethod methodsFor: 'printing'!
agent
agent = nil ifTrue: [agent := self].
^agent!
who
"Answer an Array of the class in which the receiver is defined and
the selector to which it corresponds. If the receiver is not defined
in any class, answer nil."
| sel |
sel := mclass selectorAtMethod: self agent ifAbsent: [nil].
^sel == nil
ifTrue: [nil]
ifFalse: [Array with: mclass with: sel]!
printNameOn: aStream inClass: aClass
| selector class |
aClass isNil
ifTrue:
[| who |
who := self who.
who isNil
ifTrue:
[class := mclass.
selector := class defaultSelectorForMethod: self agent]
ifFalse:
[class := who first.
selector := who at: 2]]
ifFalse:
[class := aClass.
selector := class
selectorAtMethod: self agent
setClass: [:mc | ]].
aStream nextPutAll: class name.
mclass == class
ifFalse:
[aStream nextPut: $(.
aStream nextPutAll: mclass name.
aStream nextPut: $)].
aStream nextPutAll: '>>'.
aStream nextPutAll: selector!
agent: aCompiledMethod
agent := aCompiledMethod! !
!Behavior methodsFor: 'accessing method dictionary'!
setBreakpointAt: aSelector
| c m isBreakpoint |
c := self whichClassIncludesSelector: aSelector.
c isNil ifTrue: [^self].
m := c compiledMethodAt: aSelector.
isBreakpoint := m isBreakpoint.
self == c
ifTrue: [
isBreakpoint
ifTrue: [m client mclass == self
ifTrue: [m client agent: m client.
self addSelector: aSelector withMethod: m client]
ifFalse: [self removeSelector: aSelector]]
ifFalse: [self addSelector: aSelector withMethod: (BreakpointMethod on: m selector: aSelector inClass: self)]]
ifFalse: [
isBreakpoint ifTrue: [m := m client].
self addSelector: aSelector withMethod:
(BreakpointMethod on: m selector: aSelector inClass: self)]! !
!Behavior methodsFor: 'compiling'!
breakpointCompilerClass
"Answer a compiler class appropriate for source methods of this class."
^BreakpointCompiler! !
!Behavior methodsFor: 'testing method dictionary'!
isBreakpointAt: aSymbol
^(self includesSelector: aSymbol)
and: [(self compiledMethodAt: aSymbol) isBreakpoint]! !
!Inspector methodsFor: 'field list'!
fieldMenu
"Answer a Menu of operations on the variables that is to be displayed
when the operate menu button is pressed."
"Inspector flushMenus"
field == nil ifTrue: [^ nil].
ListMenu == nil ifTrue:
[ListMenu := PopUpMenu
labelList: #((inspect draw browseLightweight))
values: #(inspectField drawField browseLightweightField)].
^ ListMenu! !
!Inspector methodsFor: 'private-menu messages'!
browseLightweightField
self fieldValue browseLightweight! !
!DictionaryInspector methodsFor: 'field list'!
fieldMenu
"DictionaryInspector flushMenus"
field == nil ifTrue:
[^PopUpMenu labels: 'add field' withCRs
values: #(addField)].
DictListMenu == nil ifTrue:
[DictListMenu := PopUpMenu
labels: 'inspect\draw\browseLightweight\references\add field\remove' withCRs
lines: #(4)
values: #(inspectField drawField browseLightweightField browseReferences
addField removeField)].
^DictListMenu! !
!ContextInspector methodsFor: 'field list'!
fieldMenu
"Answer a Menu of operations on variables that is to be displayed
when the operate menu button is pressed."
field == nil ifTrue: [^ nil].
^PopUpMenu
labelList: #((inspect draw browseLightweight))
values: #(inspectField drawField browseLightweightField)! !
!Browser methodsFor: 'selector list'!
selectorMenu
"Answer a Menu of operations on message selectors to be
displayed when the operate menu button is pressed."
"Browser flushMenus"
selector == nil ifTrue: [^ nil].
MessageMenu == nil ifTrue:
[MessageMenu := PopUpMenu
labels: 'file out as...\hardcopy\spawn\senders\implementors\messages...\breakpoint\move to...\remove...' withCRs
lines: #(3 6)
values: #(fileOutMessage printOutMessage spawnMethod browseSenders browseImplementors browseMessages breakpointMethod moveMethod removeMethod)].
^ MessageMenu! !
!Browser methodsFor: 'private-selector functions'!
breakpointMethod
selector isNil ifTrue: [^self].
self selectedClass setBreakpointAt: selector.
self changed: #selector!
formatSelector: aSymbol
| symbol mclass c |
c := self selectedClass.
mclass := c whichClassIncludesSelector: aSymbol.
symbol := (((mclass isBreakpointAt: aSymbol)
ifTrue: ['*']
ifFalse: [' ']), aSymbol) asText.
^mclass = c
ifTrue: [symbol allBold]
ifFalse: [symbol]! !
!Browser class methodsFor: 'private-view creation'!
addFormattedSelectorViewTo: aContainer in: area on: aBrowser readOnly: readOnly
| edgeDecorator view |
view := FormattedListView on: aBrowser printItems: false oneItem: readOnly
aspect: #selector change: #selector: list: #selectorList
menu: #selectorMenu initialSelection: #selector.
view printReceiver: aBrowser printMessage: #formatSelector:.
edgeDecorator := LookPreferences edgeDecorator on: view.
readOnly ifTrue: [edgeDecorator noVerticalScrollBar].
^aContainer add: edgeDecorator in: area!
addSelectorViewTo: aContainer in: area on: aBrowser readOnly: readOnly
^self addFormattedSelectorViewTo: aContainer in: area on: aBrowser readOnly: readOnly!
addUnformattedSelectorViewTo: aContainer in: area on: aBrowser readOnly: readOnly
| edgeDecorator |
edgeDecorator := LookPreferences edgeDecorator on:
(SelectionInListView on: aBrowser printItems: false oneItem: readOnly
aspect: #selector change: #selector: list: #selectorList
menu: #selectorMenu initialSelection: #selector).
readOnly ifTrue: [edgeDecorator noVerticalScrollBar].
^aContainer add: edgeDecorator in: area! !
!NotifierView class methodsFor: 'instance creation'!
handleBreakpoint
| displayPoint haltContext haltMethod restartMethod newContext aDebugger |
haltContext := thisContext sender.
haltMethod := (haltContext receiver dispatchingClass whichClassIncludesSelector: haltContext selector) compiledMethodAt: haltContext selector.
restartMethod := haltMethod client.
restartMethod frameSize > haltContext size
ifTrue: [
newContext := haltContext resizedWith: restartMethod.
newContext restart.
haltContext terminate]
ifFalse: [
newContext := haltContext restartWith: restartMethod].
"Make sure that controllers without views are removed"
ScheduledControllers removeInvalidControllers.
displayPoint :=
(ScheduledControllers activeControllerProcess ~~ Processor activeProcess
or: [ScheduledControllers activeController == nil])
ifTrue: [Screen default bounds center]
ifFalse: [| view |
view := ScheduledControllers activeController view.
view displayBox center].
aDebugger := Debugger breakpointedContext: newContext proceedable: true.
self openDebugger: aDebugger
contents: (self shortStackFor: newContext)
label: 'Break Point in ', haltMethod mclass printString, '>>', haltContext selector
displayAt: displayPoint.
Processor activeProcess suspend! !
Browser flushMenus!
Inspector flushMenus!
!SmalltalkCompiler methodsFor: 'public access'!
evaluate: textOrStream in: aContext receiver: receiver notifying: aRequestor ifFail: failBlock
"Compiles the sourceStream into a parse tree, then generates code
into a method. If receiver is not nil, then the text can refer to
instance variables of that receiver (the Inspector uses this). If
aContext is not nil, the text can refer to temporaries in that context
(the Debugger uses this). If aRequestor is not nil, then it will
receive a notify:at: message before the attempt to evaluate is aborted.
Finally, the compiled method is invoked and the value returned"
| methodNode method |
class := (aContext == nil
ifTrue: [receiver]
ifFalse: [aContext homeReceiver]) dispatchingClass.
self from: textOrStream
class: class
context: aContext
notifying: aRequestor.
methodNode := self translate: sourceStream noPattern: true ifFail: [^failBlock value].
method := methodNode generate.
^context == nil
ifTrue: [receiver performMethod: method]
ifFalse: [receiver performMethod: method with: context]! !
!Context methodsFor: 'printing'!
printOn: aStream
method printNameOn: aStream inClass: self homeReceiver dispatchingClass! !
!Context methodsFor: 'simulation-primitives'!
primPerform: rcvr selector: selector numArgs: numArgs
self findMethod: selector
class: rcvr dispatchingClass
ifFound:
[:meth :mclass |
numArgs = meth numArgs ifFalse:
[self error: 'Wrong numArgs for perform'.
"To continue, just return the receiver of the message."
stackp := stackp - numArgs - 1.
^self].
"Squeeze the selector out of the stack"
numArgs negated to: -1 do: [:i | self localAt: stackp + i put: (self localAt: stackp + i + 1)].
stackp := stackp - 1.
^self runMethod: meth numArgs: numArgs contextClass: MethodContext].
self error: 'Message not found: ', selector printString.
"To continue, just return the receiver of the message."
stackp := stackp - numArgs - 1! !
!Context methodsFor: 'simulation-control'!
send: selector numArgs: na
| rcvr |
rcvr := self localAt: stackp - na.
^self
send: selector
receiver: rcvr
class: rcvr dispatchingClass
super: false
numArgs: na! !
!MethodContext methodsFor: 'accessing'!
selector
"Answer the selector of the method that created the receiver."
^receiver dispatchingClass
selectorAtMethod: method
setClass: [:ignored]!
sourceCode
"Answer the source form of the receiver's method."
| mclass selector |
^method getSourceForUserIfNone:
[selector := self receiver dispatchingClass selectorAtMethod: method setClass: [:mc | mclass := mc].
mclass sourceCodeForMethod: method at: selector]! !
!BlockContext methodsFor: 'accessing'!
selector
"Answer the selector of the method that created the receiver."
| home classOfMethod |
home := self home.
home notNil
ifTrue:
[^home receiver dispatchingClass
selectorAtMethod: home method
setClass: [:ignored]].
classOfMethod := self mclass.
^classOfMethod parserClass new parseSelector: self sourceCode! !
!Debugger methodsFor: 'context list'!
context: aContext
"Set aContext to be the currently viewed context.
This involves resetting all the inspectors, the viewed
source code, and the exception handling signals."
| oldContext class receiver |
oldContext := context.
context := aContext.
self changed: #theContext.
context == nil
ifTrue:
[contextInspector inspect: nil.
receiverInspector inspect: nil.
self changed: #text.
^self].
receiver := self contextReceiver.
class := (receiver == nil
ifTrue: [context mclass] ifFalse: [receiver dispatchingClass]).
Metaclass obsoleteSignal
handle: [:ex | className := context mclass]
do:
[meta := class isMeta.
meta
ifTrue: [className := class soleInstance name]
ifFalse: [className := class name].
selector := context selector.
(oldContext == nil or: [oldContext method ~~ context method])
ifTrue:
[sourceCode := context sourceCode.
sourceMap := context sourceMap. "will compute tempNames"
self changed: #text]].
receiver == nil
ifTrue: [receiverInspector inspect: nil.
receiverInspector changed: #empty]
ifFalse: [receiverInspector inspect: receiver].
contextInspector inspect: context.
CompiledCode nPCMapErrorSignal
handle: [:ex | DialogView warn: ex errorString]
do: [self changed: #pc]! !
!Debugger methodsFor: 'private-menu messages'!
correct: aNotifierController
"Attempt to correct the spelling of the not-understood message and resend."
| oldSelector oldFirst oldArgs selectors guess score bestScore |
processHandle topContext selector == #doesNotUnderstand:
ifFalse: [^ aNotifierController view flash].
oldSelector := (processHandle topContext tempAt: 1) selector.
oldFirst := oldSelector first.
oldArgs := oldSelector numArgs.
selectors := processHandle topContext receiver dispatchingClass allSelectors select:
[:sel | sel first = oldFirst and: [sel numArgs = oldArgs]].
bestScore := 0.
selectors do:
[:sel |
(score := sel spellAgainst: oldSelector) > bestScore ifTrue:
[bestScore := score. guess := sel]].
(DialogView confirm: 'retry with selector:
', guess) ifFalse: [^ aNotifierController view flash].
processHandle topContext tempAt: 1 put:
(Message selector: guess arguments: (processHandle topContext tempAt: 1) arguments).
^ self proceed! !
!Debugger class methodsFor: 'instance creation'!
breakpointedContext: aContext proceedable: aBoolean
| aDebugger |
aDebugger := self new.
aDebugger
process: Processor activeProcess
context: aContext
interrupted: true
proceedable: aBoolean.
^aDebugger! !
Compiler subclass: #BreakpointCompiler
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Object Debugging'!
!BreakpointCompiler methodsFor: 'private'!
newCodeStream
"Return an appropriate code stream"
^CodeStream new methodClass: BreakpointMethod! !
Behavior subclass: #LightweightClass
instanceVariableNames: 'name '
classVariableNames: 'ClassMethod '
poolDictionaries: ''
category: 'Object Debugging'!
!LightweightClass methodsFor: 'instance creation'!
initializeWithSuper: aClass
| md |
self superclass: aClass.
md := MethodDictionary new.
md at: #class
put: (ClassMethod copy mclass: self).
self methodDictionary: md.
format := (aClass instSize bitAnd: 255) bitOr: -4096.
self name: '{', aClass name, '}'.! !
!LightweightClass methodsFor: 'naming'!
name
^name!
name: aSymbol
name := aSymbol! !
!LightweightClass methodsFor: 'printing'!
printOn: aStream
"Append to the argument aStream a sequence of characters that identifies the receiver."
aStream nextPutAll: self name! !
!LightweightClass methodsFor: 'testing'!
isMeta
^false! !
!LightweightClass methodsFor: 'compiling'!
compile: code notifying: requestor ifFail: failBlock
"Compile the argument, code, as source code in the context of the receiver and
install the result in the receiver's method dictionary. The argument requestor is to
be notified if an error occurs. The argument code is either a string or an
object that converts to a string or a PositionableStream on an object that
converts to a string. This method does not save the source code.
Evaluate the failBlock if the compilation does not succeed."
| methodNode selector save method oldMethod |
save := code asString copy.
methodNode := self compilerClass new
compile: code
in: self
notifying: requestor
ifFail: failBlock.
selector := methodNode selector.
method := methodNode generate.
method sourceCode: save.
oldMethod := self compiledMethodAt: selector ifAbsent: [nil].
(oldMethod notNil and: [oldMethod isBreakpoint])
ifTrue: [oldMethod client: method]
ifFalse: [
self addSelector: selector withMethod: method].
^selector!
compilerClass
^LightweightCompiler! !
"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
LightweightClass class
instanceVariableNames: ''!
!LightweightClass class methodsFor: 'class initialization'!
initialize
"LightweightClass initialize"
ClassMethod := (LightweightCompiler new
compile: 'class ^self dispatchingClass superclass'
in: Object
notifying: nil
ifFail: []) generate.
ClassMethod sourceCode: 'class
^self dispatchingClass superclass'! !
!LightweightClass class methodsFor: 'instance creation'!
newWithSuper: aClass
^self basicNew initializeWithSuper: aClass! !
CompiledMethod variableSubclass: #CompiledMethodWithSource
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Object Debugging'!
!CompiledMethodWithSource methodsFor: 'source code management'!
getSource
^sourceCode!
methodWithSource
sourceCode notNil ifTrue: [^self].
^nil!
sourceCode: aString
sourceCode := aString! !
Compiler subclass: #LightweightCompiler
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Object Debugging'!
!LightweightCompiler methodsFor: 'private'!
newCodeStream
"Return an appropriate code stream"
^CodeStream new methodClass: CompiledMethodWithSource! !
CompiledMethod variableSubclass: #BreakpointMethod
instanceVariableNames: 'clientMethod '
classVariableNames: ''
poolDictionaries: ''
category: 'Object Debugging'!
!BreakpointMethod methodsFor: 'initialization'!
bytes: aByteString mclass: aClass sourceCode: aSourcePointer
bytes := aByteString.
mclass := aClass.
sourceCode := aSourcePointer!
client: aCompiledMethod
clientMethod := aCompiledMethod.
aCompiledMethod agent: self! !
!BreakpointMethod methodsFor: 'accessing'!
client
^clientMethod! !
!BreakpointMethod methodsFor: 'source code management'!
getSource
"Answer the source code for the receiver. Answer nil if this method
has no stored source."
^clientMethod getSource! !
!BreakpointMethod methodsFor: 'testing'!
isBreakpoint
^true! !
"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
BreakpointMethod class
instanceVariableNames: ''!
!BreakpointMethod class methodsFor: 'instance creation'!
on: aCompiledMethod selector: sel inClass: class
| b |
b := (class breakpointCompilerClass new
compile: (self codeStringFor: sel)
in: class
notifying: nil
ifFail: []) generate.
^b client: aCompiledMethod! !
!BreakpointMethod class methodsFor: 'private'!
codeStringFor: sel
| k prefix count |
sel isInfix
ifTrue: [prefix := sel, ' arg1 ']
ifFalse: [
sel isKeyword
ifFalse: [prefix := sel, ' ']
ifTrue: [k := sel keywords.
prefix := ''.
count := 1.
k do: [ :x | prefix := prefix, ' ', x, ' arg', count printString, ' '.
count := count + 1]]].
^prefix, 'NotifierView handleBreakpoint'! !
Browser subclass: #LightweightClassBrowser
instanceVariableNames: 'lightweightClass listUpToClass '
classVariableNames: 'LightweightSelectorMenu '
poolDictionaries: ''
category: 'Object Debugging'!
!LightweightClassBrowser methodsFor: 'initialize-release'!
onObject: anObject
"Set the receiver to be a browser on anObject's lightweight class (this method assumes
the object has a lightweight class, which is guaranteed in the class method newOnObject:),
so that therefore the organization is the system organizer."
lightweightClass := anObject dispatchingClass.
listUpToClass := lightweightClass.
className := lightweightClass name.
textMode := #methodDefinition! !
!LightweightClassBrowser methodsFor: 'class list'!
className: selection
"Set the receiver's currently selected class to be selection and
update the message category list. If this class no longer exists,
print a message to that effect in the system transcript, if it is open."
self halt!
selectedClass
"Answer the class object that is currently selected."
^lightweightClass! !
!LightweightClassBrowser methodsFor: 'doIt/accept/explain'!
acceptText: aText from: aController
"Text has been changed. Store or compile the text, depending on
the current mode of the receiver."
textMode == #methodDefinition ifTrue:
[^ self acceptMethod: aText from: aController].
self halt.
^ false! !
!LightweightClassBrowser methodsFor: 'selector list'!
newSelectorList: initialSelection
"Set the currently selected message selector to be initialSelection."
selector := initialSelection.
self changed: #selector!
selector: selection
"Set the receiver's currently selected message selector to be
selection. If the selection has been separately removed from the
system, then print a message to that effect in the system
transcript, if it is open."
selector := selection.
Dictionary keyNotFoundSignal
handle:
[:ex |
DialogView warn: 'selector ' , selector , ' no longer exists.'.
ex return]
do: ["KeyNotFoundSignal is raised when the selector name
selected
in a browser is already removed in another browser."
self textMode: #methodDefinition]!
selectorList
"Answer the sequenceable collection containing the message selectors that
the receiver accesses via the currently selected class and message category."
| selSet selClass |
selClass := self selectedClass.
selSet := selClass selectors asSet.
[selClass = listUpToClass]
whileFalse: [ selClass := selClass superclass.
selSet addAll: selClass selectors].
^selSet asSortedCollection!
selectorMenu
"Answer a Menu of operations on message selectors to be
displayed when the operate menu button is pressed."
"ObjectBrowser flushMenus"
selector == nil ifTrue: [^ nil].
LightweightSelectorMenu == nil ifTrue:
[LightweightSelectorMenu := PopUpMenu
labels: 'hardcopy\senders\implementors\messages...\breakpoint\remove...' withCRs
lines: #(1 4 5)
values: #(printOutMessage browseSenders browseImplementors browseMessages breakpointMethod removeMethod)].
^ LightweightSelectorMenu! !
!LightweightClassBrowser methodsFor: 'private-selector functions'!
acceptMethod: aText from: aController
| newSelector |
newSelector := self selectedClass
compile: aText
notifying: aController.
newSelector == nil ifTrue: [^false].
self newSelectorList: newSelector.
^true! !
!LightweightClassBrowser methodsFor: 'category list'!
category
^'**Lightweight Class**'!
category: aSelection
^self!
categoryList
^Array with: self category!
categoryMenu
^PopUpMenu labels: 'update' values: #(updateSelectors)! !
!LightweightClassBrowser methodsFor: 'private-category functions'!
updateSelectors
self newSelectorList: selector! !
!LightweightClassBrowser methodsFor: 'superclass list'!
superclass
^listUpToClass!
superclass: aSelection
"listUpToClass := aSelection.
self newSelectorList: selector"
^self!
superclassList
^Array with: listUpToClass!
superclassMenu
| c choice |
c := self selectedClass withAllSuperclasses.
choice := (PopUpMenu labelArray: (c collect: [ :x | x name])) startUp.
choice = 0
ifTrue: [^nil].
listUpToClass := c at: choice.
self changed: #superclass.
self changed: #selector.
^nil! !
!LightweightClassBrowser methodsFor: 'text'!
text
| c |
selector == nil
ifTrue: [^ self class sourceCodeTemplate asText]
ifFalse: [
c := self selectedClass whichClassIncludesSelector: selector.
^ c sourceMethodAt: selector]! !
"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!
LightweightClassBrowser class
instanceVariableNames: ''!
!LightweightClassBrowser class methodsFor: 'class initialization'!
flushMenus
"Cause all menus to be newly created (so changes appear)."
"ObjectBrowser flushMenus."
super flushMenus.
LightweightSelectorMenu := nil! !
!LightweightClassBrowser class methodsFor: 'instance creation'!
newOnObject: anObject
"Create and schedule a view that is a browser for anObject."
self openObjectBrowserOn: anObject becomeLightweight! !
!LightweightClassBrowser class methodsFor: 'view creation'!
openObjectBrowserOn: anObject
"Create and schedule a browser on the lightweight class of anObject."
self openObjectBrowserOn: anObject withTextState: nil!
openObjectBrowserOn: anObject withTextState: anArray
"Create and schedule a browser on the lightweight class currently selected by aBrowser.
anArray holds the initial text state."
| aBrowser lineHeightBlock topView topWindow |
lineHeightBlock := self lineHeightBlock.
aBrowser := self new onObject: anObject.
topWindow := ScheduledWindow model: aBrowser
label: 'Lightweight Class Browser'
minimumSize: 400@250.
topView := DependentComposite new.
self
addCategoryViewTo: topView
in: (LayoutFrame new
leftOffset: 0;
topOffset: 0;
rightFraction: 0.5;
bottomOffset: lineHeightBlock) on: aBrowser readOnly: true;
addSuperclassListViewTo: topView
in: (LayoutFrame new
leftFraction: 0.5;
topOffset: 0;
rightFraction: 1.0;
bottomOffset: lineHeightBlock) on: aBrowser readOnly: true;
addFormattedSelectorViewTo: topView
in: (LayoutFrame new
leftOffset: 0;
topOffset: lineHeightBlock;
rightFraction: 1.0;
bottomFraction: 0.4) on: aBrowser readOnly: false;
addTextViewTo: topView in: (0@0.4 corner: 1.0@0.75) on: aBrowser initialSelection: nil initialState: anArray.
Inspector view: (Inspector inspect: anObject) in: (0@0.75 corner: 1.0@1.0) of: topView.
topWindow component: topView.
topWindow icon: (Icon constantNamed: #classBrowser).
topWindow openWithExtent: ((topWindow minimumSize * 3 + topWindow maximumSize) // 4)! !
!LightweightClassBrowser class methodsFor: 'private-view creation'!
addSuperclassListViewTo: aContainer in: area on: aBrowser readOnly: readOnly
| edgeDecorator |
edgeDecorator := LookPreferences edgeDecorator on:
(SelectionInListView on: aBrowser printItems: true oneItem: readOnly
aspect: #superclass change: #superclass: list: #superclassList
menu: #superclassMenu initialSelection: #superclass).
readOnly ifTrue: [edgeDecorator noVerticalScrollBar].
^aContainer add: edgeDecorator in: area! !
SelectionInListView subclass: #FormattedListView
instanceVariableNames: 'printMsg printReceiver sendParameter '
classVariableNames: ''
poolDictionaries: ''
category: 'Object Debugging'!
!FormattedListView methodsFor: 'initialization'!
initialize
super initialize.
printReceiver := nil.
printMsg := #printString.
sendParameter := false!
printBlock: aBlock
self receiver: aBlock message: #value: useParameter: true!
printMessage: aSelector
self receiver: nil message: aSelector useParameter: false!
printReceiver: anObject printMessage: aSelector
self receiver: anObject message: aSelector useParameter: true!
receiver: aReceiver message: aSelector useParameter: aBoolean
printReceiver := aReceiver.
printMsg := aSelector.
sendParameter := aBoolean.
self list: self getList! !
!FormattedListView methodsFor: 'list access'!
displayableLinesFrom: anArray
"Answer a collection of displayable lines from anArray."
^sendParameter
ifTrue: [anArray collect: [ :elt | printReceiver perform: printMsg with: elt]]
ifFalse: [anArray collect: [ :elt | elt perform: printMsg]]! !
LightweightClass initialize!